iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

一些讓你看來很強的全端- trcp 伴讀系列 第 12

Day-012. 一些讓你看來很強的全端 TRPC 伴讀 - Links

  • 分享至 

  • xImage
  •  

今天要來介紹一個 trpc 一個很特的功能 Links

Links Overview

Links 他是一個 trpcclient 端跟 server 端資料流的方式,link 會被定義在 client 端中,每一個 link 只會做一件事情,他可以是你呼叫 trpc 中對於 websocket 的連線,或是一些 logging 內容。

Link 他在 client 端中會議定義一個 array 叫做 link chain,在代表著每次 trpc client 都會根據 array 順序依序呼叫 link function

這樣說可能讀者會不清楚沒關係我們簡單 demo 一下~

loggerLink

讀者可以以昨天範例當例子,trpc 很貼心的有預設提供一些 link ,例如 loggerLink 用來記錄每次 useQuery 或是 useMutation 的操作。

// ~src/utils/api.ts

import { httpBatchLink, httpLink, loggerLink } from '@trpc/client';
export const api = createTRPCNext<AppRouter>({
  config(opts) {
    return {
      links: [
        // loggerLink 在 array 順序中不能大於 httpBatchLink ,下方會說明
        loggerLink(),
        httpBatchLink({
          /**
           * If you want to use SSR, you need to use the server's full URL
           * @link https://trpc.io/docs/ssr
           **/
          url: `${getBaseUrl()}/api/trpc`,
          // maxURLLength: 2083, // 限制 413 Payload Too Large、414 URI Too Long和404 Not Found
          // You can pass any HTTP headers you wish here
          async headers() {
            return {
              // authorization: getAuthCookie(),
            };
          },
        }),
      ],
      queryClient
    };
  }
});

這時我們新增一比 title5 的資料

然後看 console.log 你會發現 loggerLink 做的事情就是紀錄我們 useQueryuseMutate 的操作,這樣開發就可以知道他 input 內容是什麼,共用 context 有哪些非常方便。

如果你不希望在 prod 中看到 log 你可以透過 enabled 去判斷。

import { createTRPCProxyClient, httpBatchLink, loggerLink } from '@trpc/client';
import type { AppRouter } from '../server';
const client = createTRPCProxyClient<AppRouter>({
  links: [
    /**
     * The function passed to enabled is an example in case you want to the link to
     * log to your console in development and only log errors in production
     */
    loggerLink({
      enabled: (opts) =>
        (process.env.NODE_ENV === 'development' &&
          typeof window !== 'undefined') ||
        (opts.direction === 'down' && opts.result instanceof Error),
    }),
    httpBatchLink({
      url: 'http://localhost:3000',
    }),
  ],
});

httpBatchLink

這時眼睛很大的小夥伴一定會發現在 link 中有一個預設的 httpBatchLink 這個又是什麼?別及~讓我慢慢介紹~在 link 中有一個特別的 link type 叫做 terminating link

terminating link :

  1. 他是一個終止的 link ,如果你要在 trpc client 中使用其他的 link 內容例如上面說的 loggerLink 一定要加,如果不加 trpc client 的操作將不會發送到 trpc server 中,所以預設每個 client 一定會有 terminating link
  2. terminating link 會用來處理 https connext 的 info,或是 websocket 的連線。
  3. httpBatchLink 會是推薦的 terminating link 其次是 httpLinkwsLink

街遮我們來看 httpBatchLink 到底做什麼事情,
httpBatchLink 主要是批次處理所有 trpc client 請求整理成一次的 api request,有點像是 promise.allclient 端一次接收,而 httpBatchLink 則是相反,我們來看例子。

Home page 中我們在請求其他的 query

// ~src/pages/index
export default function Home() {
  const utils = api.useContext()
  const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery()
  const { data: greetingdata } = api.greeting.useQuery({ name: 'Danny' })
  return (
  //..
  )
}

你會發現 request 只會有一筆

response 結果也幫你整理成 array

批次處理的好處是我們可以減少 request 的請求,減少 server 的浪費的浪費,這樣你的 request 就不會有 block 的問題。

蠻有趣的是你可以透過 Promise.all 選擇哪些 request 需要 batch 一起。
``

const somePosts = await Promise.all([
  trpc.post.byId.query(1),
  trpc.post.byId.query(2),
  trpc.post.byId.query(3),
]);

如果所有 request 都 batch 其中一個 error 會怎麼辦?

你可能會有疑惑如果我用了 httpBatchLink 那如果我在同一個頁面中假如呼叫 10 個 api 其中一個發生 error 會不會造成全部 api 都抓不到資料?答案是不會~筆者可以簡單給你看 demo

我們去找一個不存在的 post ,這時我們看一下 network

// ~src/pages/index
export default function Home() {
  const utils = api.useContext()
  const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery()
  const { data: greetingdata } = api.greeting.useQuery({ name: 'Danny' })
  const { data: postData } = api.posts.getPost.useQuery({ post_id: 'not_found_id' })
  return (
  //..
  )
}

你會發現只會有特定的 resulterror ,其他的 query 或是 mutate 都不會有影響。

我可不可以不要用 httpBatchLink ?

當然是可以,你可以在 api handler 取消 enable

export default createNextApiHandler({
  router: appRouter,
  createContext: createTRPCContext,
  batching: {
    enabled: false,
  },
});

或是把 httpBatchLink 改成 httpLink

import type { AppRouter } from '@/server/routers/app';
import { httpLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
export const trpc = createTRPCNext<AppRouter>({
  config() {
    return {
      links: [
        httpLink({
          url: '/api/trpc',
        }),
      ],
    };
  },
});

甚至把 httpBatchLink 改成 httpLink


import { httpBatchLink, httpLink, loggerLink } from '@trpc/client';
const client = createTRPCProxyClient<AppRouter>({
  links: [
    httpLink({
      url: 'http://localhost:3000',
    }),
  ],
});

這樣每個 request 就獨立摟~

可以限制 httpBatchLink 大小嗎?

如果 batch 得大小太大會造成 413 Payload Too Large414 URI Too Long404 Not Found 這些 error code 問題,httpBatchLink 中有提供第二個參數 maxURLLength 幫你限制每次 batch 數量已減少 error code 發生。

import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server';
const client = createTRPCProxyClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000',
      maxURLLength: 2083, // a suitable size
    }),
  ],
});

或是你可以針對某一個 query 不是參與 batch,這時你可以用,splitLink 根據每個 request context 決定要用 httpBatchLink 或是 httpLink

如果 splitLink 中 return true 則執行 truecallback functions,反之則是 false

import {
  createTRPCProxyClient,
  httpBatchLink,
  httpLink,
  splitLink,
} from '@trpc/client';
import type { AppRouter } from '../server';
const url = `http://localhost:3000`;
const client = createTRPCProxyClient<AppRouter>({
  links: [
        loggerLink({
          enabled: (opts) =>
            (process.env.NODE_ENV === 'development' &&
              typeof window !== 'undefined') ||
            (opts.direction === 'down' && opts.result instanceof Error),
        }),
        splitLink({
          condition(op) {
            // check for context property `skipBatch`
            return op.context.skipBatch === true;
          },
          // when condition is true, use normal request
          true: httpLink({
            /**
             * If you want to use SSR, you need to use the server's full URL
             * @link https://trpc.io/docs/ssr
             **/
            url: `${getBaseUrl()}/api/trpc`,
            // You can pass any HTTP headers you wish here
            async headers() {
              return {
                // authorization: getAuthCookie(),
              };
            }
          }),
          // when condition is false, use batching
          false: httpBatchLink({
            /**
             * If you want to use SSR, you need to use the server's full URL
             * @link https://trpc.io/docs/ssr
             **/
            url: `${getBaseUrl()}/api/trpc`,
            async headers() {
              return {
                // authorization: getAuthCookie(),
              };
            },
            // maxURLLength: 2083, // 限制 413 Payload Too Large、414 URI Too Long和404 Not Found
            maxURLLength: 2083
          }),
        }),
      ],
});

這樣在 client 端修改 context 內容後就可以選擇哪些 request 不需要做 batch 了。

  const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery(undefined, {
    trpc: {
      context: {
        skipBatch: true,
      }
    }
  })

那如果讀者想自己客製化 link 的話可以去 doc 看看,這邊就不多做說明。

import { TRPCLink } from '@trpc/client';
import { observable } from '@trpc/server/observable';
import type { AppRouter } from 'server/routers/_app';
export const customLink: TRPCLink<AppRouter> = () => {
  // here we just got initialized in the app - this happens once per app
  // useful for storing cache for instance
  return ({ next, op }) => {
    // this is when passing the result to the next link
    // each link needs to return an observable which propagates results
    return observable((observer) => {
      console.log('performing operation:', op);
      const unsubscribe = next(op).subscribe({
        next(value) {
          console.log('we received value', value);
          observer.next(value);
        },
        error(err) {
          console.log('we received error', err);
          observer.error(err);
        },
        complete() {
          observer.complete();
        },
      });
      return unsubscribe;
    });
  };
};

相關連結

https://github.com/Danny101201/next_demo/tree/main

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day-011. 一些讓你看來很強的全端 TRPC 伴讀 - TodoList (下)
下一篇
Day-013. 一些讓你看來很強的全端 TRPC 伴讀 -Prefetch & Cache
系列文
一些讓你看來很強的全端- trcp 伴讀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言